#include "cli_socket_handle.h"
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdarg.h>
#include <stdio.h>
#include "rsa.h"
#include "user.h"
#include <sys/stat.h>
#include <errno.h>
#include <sys/mman.h>
#include <fcntl.h>
#include "utility.h"
#include "sha2.h"
#include "log.h"
#include "userSqliteOp.h"
 

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic warning "-Wunused-variable"
#pragma GCC diagnostic warning "-Wformat"
#pragma GCC diagnostic warning "-Wdeprecated-declarations"
#pragma GCC diagnostic warning "-Wunreachable-code"
#pragma GCC diagnostic warning "-Waddress"
#pragma GCC diagnostic warning "-Warray-bounds"
#pragma GCC diagnostic warning "-Wchar-subscripts"
#pragma GCC diagnostic warning "-Wimplicit-int"
#pragma GCC diagnostic warning "-Wimplicit-function-declaration"
#pragma GCC diagnostic warning "-Wcomment"
#pragma GCC diagnostic warning "-Wmain"
#pragma GCC diagnostic warning "-Wmissing-braces"
#pragma GCC diagnostic warning "-Wnonnull"
#pragma GCC diagnostic warning "-Wparentheses"
#pragma GCC diagnostic warning "-Wpointer-sign"
#pragma GCC diagnostic warning "-Wreturn-type"
#pragma GCC diagnostic warning "-Wsequence-point"
#pragma GCC diagnostic warning "-Wsign-compare"
#pragma GCC diagnostic warning "-Wstrict-aliasing"
#pragma GCC diagnostic warning "-Wswitch"
#pragma GCC diagnostic warning "-Wtrigraphs"
#pragma GCC diagnostic warning "-Wuninitialized"
#pragma GCC diagnostic warning "-Wunknown-pragmas"
#pragma GCC diagnostic warning "-Wunused-label"
#pragma GCC diagnostic warning "-Wunused-value"

#pragma GCC diagnostic error "-Wformat"
#pragma GCC diagnostic error "-Wdeprecated-declarations"
#pragma GCC diagnostic error "-Wunreachable-code"

// 被夹在这中间的代码针对于此警告都会忽视不显示出来
 //常见警告的名称
//1.声明变量未使用  "-Wunused-variable"
//2.方法定义未实现  "-Wincomplete-implementation"
//3.未声明的选择器  "-Wundeclared-selector"
//4.参数格式不匹配  "-Wformat"
//5.废弃掉的方法     "-Wdeprecated-declarations"
//6.不会执行的代码  "-Wunreachable-code"
//7.忽略在arc 环境下performSelector产生的 leaks 的警告 "-Warc-performSelector-leaks"
//8.忽略类别方法覆盖的警告 "-Wobjc-protocol-method-implementation"（修复开源库bug，覆盖开源库方法时会用到）

#include "sqliteUtility.h"
#include "KernelList.h"

#if 0
typedef struct _ONE_PERSON_
{
    u_int64_t userId;
    u_int64_t joinTime;
    char name[NAME_LEN];
    struct list_head node;
}ONE_PERSON;
typedef struct _ONE_MESSAGE_
{
    u_int64_t userId;           //发送者id
    char name[NAME_LEN];        //发送者姓名
    u_int64_t sendTime;         //发送时间
    u_int64_t messageType;      //消息类型
    char content[CONTENT_LEN];  //消息内容
    struct list_head node;
}ONE_MESSAGE;
typedef struct _DIALOG_LIST_
{
    u_int64_t chartId;
    u_int64_t createTime;
    struct list_head one_person;
    struct list_head one_message;
    struct list_head node;
}DIALOG_LIST;
#endif

struct CLIENT{
    FILE *fp;
    u_int64_t chartId;
    FILE_TRANS_TASK file_task;
    int messageUpdated;
    struct list_head dialogList;
    USER user;
    char m_session[NAME_LEN];
};

struct CLIENT * s_client_data;

/**
 * @brief  初始化用户数据与工作路径
 * @note   
 * @param  *name: 
 * @retval 
 */
int client_file_path_init(const char *name)
{
    int fd = 0;
    int first_flag = 1;
    char name_file[128];
    int err = snprintf(name_file, 128, "./data/%s.data", name);
    assert(err > 0);

    
    /* 1. create shm, set its size, map it, close descriptor */
    fd = open(name_file, O_RDWR | O_CREAT | O_EXCL, FILE_MODE);
    if (fd == -1 && errno == EEXIST)
    {
        fd = open(name_file, O_RDWR | O_CREAT, FILE_MODE);
        first_flag = 0;
    }

    if (fd == -1)
    {
        perror("open()");
        return -1;
    }
    s_client_data = mmap(NULL, sizeof(struct CLIENT), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (s_client_data == MAP_FAILED || s_client_data == NULL)
    {
        perror("mmap()1");
        close(fd);
        return -2;
    }
    if (ftruncate(fd, sizeof(struct CLIENT)) == -1)
    {
        perror("ftruncate()");
        close(fd);
        return -3;
    }
    close(fd);
    if (first_flag)
    {
        s_client_data->fp = NULL;
        s_client_data->file_task.up_down = FILE_NONE;
    }

    /* 3.初始化链表头 */
    INIT_LIST_HEAD(&s_client_data->dialogList);
    s_client_data->messageUpdated = 1;
    /* 2.为了测试方便，改变工作路径 */
    err = chdir(CLIENT_WORK_PATH);
    assert(err == 0);

    return 0;
}

/**
 * @brief  连接服务器，如果是UDP的话只需要创建套接字
 * @note   
 * @param  *serv_addr: 
 * @param  socket_type: 
 * @retval 
 */
int connect_file_trans_serv(struct sockaddr_in *serv_addr, int32_t socket_type)
{
    /* 1.创建套接字 */
    int sockfd = socket(AF_INET, socket_type, 0);
    assert(sockfd != -1);
    socklen_t addrlen = sizeof(struct sockaddr_in);
    serv_addr->sin_family = AF_INET;

    /* 2.设置地址重用 */
    const int on = 1;
    int err = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, (socklen_t)sizeof(on));
    if (err != 0)
    {
        fprintf(stderr, "setsockopt() SO_REUSEADDR failed\n");
        close(sockfd);
        return -1;
    }

    if (socket_type == SOCK_STREAM)
    {
        err = connect(sockfd, (struct sockaddr *)serv_addr, addrlen);
        if (err == -1)
        {
            fprintf(stderr, "ip:%s,port:%d\n", inet_ntoa(serv_addr->sin_addr), ntohs(serv_addr->sin_port));
            perror("connect server");
            exit(0);
        }
    }
    fprintf(stderr, "与服务器的 '连接' 已建立\n");
    return sockfd;
}

/**
 * @brief  用户加密登录，必须要登录以获取session后面的步骤才能进行
 * @note   
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @param  *user_name: 
 * @param  *user_passwd: 
 * @retval 
 */
int client_login(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr,
                 char *user_name, char *user_passwd)
{
    char buf[MAX_BUF_SIZE];
    char name_passwd[MAX_BUF_SIZE];

    /* 0.获取用户名与密码 */
    if(strlen(user_name) == 0 || strlen(user_name) >= NAME_PASSWD_SESSION_LEN)
    {
        memset(user_name,0,NAME_PASSWD_SESSION_LEN);
        fprintf(stderr,"\rinput user name>");
        fgets(user_name,NAME_PASSWD_SESSION_LEN,stdin);
        user_name[strlen(user_name)-1] = 0;
        if(strlen(user_name) == 0)
        {
            memcpy(user_name,"debug",6);
        }
		puts(user_name);
    }
    if(strlen(user_passwd) == 0 || strlen(user_passwd) >= NAME_PASSWD_SESSION_LEN)
    {
        memset(user_passwd,0,NAME_PASSWD_SESSION_LEN);
        fprintf(stderr,"\rinput  password>");
        fgets(user_passwd,NAME_PASSWD_SESSION_LEN,stdin);
        user_passwd[strlen(user_passwd)-1] = 0;
        if(strlen(user_passwd) == 0)
        {
            memcpy(user_passwd,"debug",6);
        }
    }

    /* 1.按格式填写姓名密码字段 */
    int len = snprintf(name_passwd, MAX_BUF_SIZE, "$%s;%s;\n", user_name, user_passwd);
    len = len + key.m_public.m_bytes - len % key.m_public.m_bytes;

    /* 2.对登陆信息进行加密 */
    int *encode = encodeMessage(len, key.m_public.m_bytes, name_passwd,
                                key.m_public.m_exponent, key.m_public.m_modulus);
    int encode_len = len / key.m_public.m_bytes * sizeof(int);

    /* 3.构造数据包 */
    memcpy(buf + Msg.m_msg_len, encode, encode_len);
    head_package(buf, E_MSG_LOGIN, encode_len);
    tcp_udp_write(sockfd, socket_type, serv_addr, buf, Msg.m_msg_len + encode_len);
    free(encode);
    return 0;
}
/**
 * @brief  读套接字，再次封装是为了兼容udp与tcp
 * @note   
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @retval None
 */
void socket_read(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr)
{
    char buf[MAX_BUF_SIZE + 6];
    char *p_buf = buf;
    int count = 0;

    socklen_t addr_len = sizeof(struct sockaddr_in);
    bzero(buf, MAX_BUF_SIZE);

    if (socket_type == SOCK_STREAM)
    {
        count = read(sockfd, buf, Msg.m_msg_len);
    }
    else
    {
        //UDP需要一次读取整个数据报
        count = recvfrom(sockfd, buf, MAX_BUF_SIZE, 0, (struct sockaddr *)serv_addr, &addr_len);
    }
    /* 1.对端发送FIN后，还向这个套接字写会收到 RST */
    if (count < 0)
    {
        perror("client read sockfd");
        close(sockfd); //关掉这个套接字
        sockfd = -1;
        return;
    }
    /* 2.对方发送了FIN,服务器读会返回0，应答后处于CLOSE_WAIT状态 */
    else if (count == 0)
    {
        fprintf(stderr, "\ra user closed  \n");
        close(sockfd); //关掉这个套接字
        sockfd = -1;
        exit(-1);
    }
    /* 3.没有读到6个字节 */
    else if (count < 6)
    {
        fprintf(stderr, "messages is less than 6\n");
        close(sockfd); //关掉这个套接字
        sockfd = -1;
        return;
    }

    /* 4.正常读数据 */
    uint16_t cmd_num = 0;
    uint32_t packet_len = 0;
    head_analyze(buf, &cmd_num, &packet_len);
    if (socket_type == SOCK_STREAM)
    {
        count = read(sockfd, buf, packet_len);
    }
    else
    {
        count -= Msg.m_msg_len;
        p_buf = p_buf + Msg.m_msg_len;
    }
    if ((unsigned int)count < packet_len)
    {
        fprintf(stderr, "cmd_num = %d,count = %d\n,packet_len = %d\n", cmd_num, count, packet_len);
        fprintf(stderr, "read failed!??\n");
        return;
    }
    deal_client_recv(sockfd, socket_type, serv_addr, cmd_num, packet_len, p_buf);
    return;
}

/**
 * @brief  保存文件任务结构体
 * @note   
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @param  *buf: 
 * @param  packet_len: 
 * @retval 
 */
int save_file_head(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr,
                   char *buf, uint32_t packet_len)
{
    if (packet_len != sizeof(FILE_TRANS_TASK))
    {
        fprintf(stderr, "packet_len != sizeof(FILE_TRANS_TASK)");
        exit(-1);
    }

    /* 1.保存文件头 */
    bzero(&s_client_data->file_task, sizeof(FILE_TRANS_TASK));
    memcpy(&s_client_data->file_task, buf, packet_len);

    /* 2.如果是客户端获取文件 */
    if (s_client_data->file_task.up_down == FILE_DOWN)
    {
        if (s_client_data->file_task.finished_size != 0)
        {
            s_client_data->fp = fopen((char *)s_client_data->file_task.file_name, "a+");
        }
        else
        {
            s_client_data->fp = fopen((char *)s_client_data->file_task.file_name, "w+");
        }
        if (s_client_data->fp == NULL)
        {
            perror("fopen()");
            exit(-1);
        }
        int err = ftruncate(fileno(s_client_data->fp), s_client_data->file_task.finished_size);
        assert(err != -1);
    }
    else if (s_client_data->file_task.up_down == FILE_UP) //如果是客户端上传文件
    {
        fprintf(stderr, "\r检测到未完成的上传，正在续传...\n");
        return continue_send_file_to_server(sockfd, socket_type, serv_addr);
    }
    else
    {
        add_log("unexpected result");
        return E_MSG_SUCCESS;
    }

    fseek(s_client_data->fp, s_client_data->file_task.finished_size, SEEK_SET);
    fprintf(stderr, "\n正在进行文件传输 %s ......\n", s_client_data->file_task.file_name);
    return E_MSG_CONTINUE;
}
/**
 * @brief  文件校验需要这个回调
 * @note   
 * @param  *fp: 
 * @param  length: 
 * @param  *data: 
 * @retval 
 */
static int get_data(void *fp, size_t length, void *data)
{
    return fread(data, 1, length, (FILE *)fp);
}
/**
 * @brief  校验文件
 * !@note  仅仅校验并输出结果，不对文件做任何修改
 * @retval 
 */
int check_file()
{
    fprintf(stderr, "\n文件传输完成，正在校验文件...\n");
    /* 1.校验文件 */
    char output[64];
    bzero(output, 64);
    fseek(s_client_data->fp, 0, SEEK_SET);
    sha256Universal(get_data, s_client_data->fp, output);
    FCLOSE(s_client_data->fp);
    if (memcmp(output, &s_client_data->file_task.sha256, 64) != 0)
    {
        fprintf(stderr, "文件不完整或经过篡改\n");
        fprintf(stderr, "check:%s\n", output);
        fprintf(stderr, "shoud:%s\ninput command>", s_client_data->file_task.sha256);
    }
    else
    {
        fprintf(stderr, "文件完整且内容未经篡改\ninput command>");
    }
    s_client_data->file_task.up_down = FILE_NONE;
    return 0;
}
/**
 * @brief  保存文件内容
 * @note   
 * @param  *buf: 
 * @param  packet_len: 
 * @retval E_MSG_CONTINUE
 */
int save_file_content(char *buf, uint32_t packet_len)
{
    if (s_client_data->fp == NULL)
    {
        fprintf(stderr, "文件未打开\n");
        exit(-1);
    }
    int num = fwrite(buf, 1, packet_len, s_client_data->fp);
    fflush(s_client_data->fp);
    if ((unsigned int)num != packet_len)
    {
        perror("fwrite()");
        fprintf(stderr, "num = %d,packet_len = %d\n", num, packet_len);
        exit(-1);
    }
    s_client_data->file_task.finished_size += num;
    fprintf(stderr, "\r已接收： %d/%d     ", s_client_data->file_task.finished_size, s_client_data->file_task.file_size);

    return E_MSG_CONTINUE;
}
/** 
 * save_user_info
 * @Descript:保存从服务器接收的用户信息结构体
 * @Author	:hezuoqiang
 * @DateTime:2019年11月1日T10:14:04+0800
 * @param	buf	:
 * @param	packet_len	:
 * @RETURN VALUE 
 * @    On success, zero is returned.  On error, 结束进程
 */
int save_user_info(char *buf, uint32_t packet_len)
{
	UTIL_ASSERT(s_client_data!=NULL);
	if (packet_len != sizeof(USER))
    {
        fprintf(stderr, "packet_len != sizeof(USER)");
        exit(-1);
    }
	bzero(&s_client_data->user, sizeof(USER));
    memcpy(&s_client_data->user, buf, packet_len);
	return E_MSG_SUCCESS;
}
void printDialogList(struct list_head *list)
{
    DIALOG_LIST *dialogListOne;
    system("clear");
    fprintf(stderr,"---dialog list---------------------------------\n");
    list_for_each_entry_reverse(dialogListOne,list,node){;
        fprintf(stderr,"\r- 会话id:%llu,创建时间:%s      ",(unsigned long long)dialogListOne->chartId,ctime((time_t *)&dialogListOne->createTime));
	}
    fprintf(stderr,"\r---------------------------------------end-----\n");
    fprintf(stderr,"\rinput command>");
}
/** 
 * printPersonList
 * @Descript:打印会话的成员列表
 * @Author	:hezuoqiang
 * @DateTime:2019年11月4日T14:51:27+0800
 * @param	list	:
 * @param	chartId	:
 * @RETURN VALUE 
 * @    On success, zero is returned.  On error, negative number is returned
 */
void printPersonList(struct list_head *list,u_int64_t chartId)
{
    DIALOG_LIST *dialogListOne = NULL;
    int findFlag = 0;
    system("clear");
    fprintf(stderr,"---dialog:%llu person list------------------------\n",(unsigned long long)chartId);
    list_for_each_entry_reverse(dialogListOne,list,node)
    {
        if(dialogListOne->chartId == chartId){
            findFlag = 1;
            break;
        }
	}
    ONE_PERSON *onePerson = NULL;
    if(dialogListOne != NULL && findFlag)
    {
        list_for_each_entry_reverse(onePerson,&dialogListOne->one_person,node)
        {
            fprintf(stderr,"\r- user id:%llu,name:%s,join time:%s",(unsigned long long)onePerson->userId,onePerson->name,ctime((time_t *)&onePerson->joinTime));
        }
    }
    fprintf(stderr,"\r---------------------------------------end-----\n");
    fprintf(stderr,"\rinput command>");
}
void printMessages(struct list_head *list,u_int64_t chartId)
{
    DIALOG_LIST *dialogListOne = NULL;
    int findFlag = 0;
    //system("clear");
    fprintf(stderr,"\r---message in dialog:%llu ----------------------\n",(unsigned long long)chartId);
    list_for_each_entry_reverse(dialogListOne,list,node)
    {
        if(dialogListOne->chartId == chartId){
            findFlag = 1;
            break;
        }
	}
    ONE_MESSAGE *oneMessage = NULL;
    if(dialogListOne != NULL && findFlag)
    {
        list_for_each_entry_reverse(oneMessage,&dialogListOne->one_message,node)
        {
            fprintf(stderr,"time:%s",ctime((time_t *)&oneMessage->sendTime));
            fprintf(stderr,"\r-> user %s: %s\n\n",oneMessage->name,oneMessage->content);
        }
    }
    fprintf(stderr,"\r---------------------------------------end-----\n");
    fprintf(stderr,"\rinput command>");
}
void saveOneMessage(char *buf,struct list_head *list,u_int64_t chartId)
{
    int findFlag = 0;
    DIALOG_LIST *dialogListOne = NULL;
    ONE_MESSAGE *oneMessage = NULL;
    list_for_each_entry_reverse(dialogListOne,list,node)
    {
        if(dialogListOne->chartId == chartId)
        {
            findFlag=1;
            break;
        }
    }

    //查找有没有重复的元素
    if(dialogListOne != NULL && findFlag)
    {
        findFlag = 1;//表示没找到
        list_for_each_entry_reverse(oneMessage,&dialogListOne->one_message,node)
        {
            //printf("%llu,%llu,%llu,%llu\n",(unsigned long long)oneMessage->userId,(unsigned long long)buf,
            //    (unsigned long long)oneMessage->sendTime,(unsigned long long)buf+sizeof(u_int64_t));
            if(memcmp(buf,&oneMessage->userId,sizeof(u_int64_t))==0 && 0==memcmp(buf+sizeof(u_int64_t),&oneMessage->sendTime,sizeof(u_int64_t)))
            {
                findFlag = 0;//表示找到了相同的元素
                break;
            }
        }
    }
    oneMessage = NULL;
    if(dialogListOne != NULL && findFlag)
    {
        s_client_data->messageUpdated = 1;
        oneMessage = malloc(sizeof(ONE_MESSAGE));
        UTIL_ASSERT(oneMessage != NULL);
        memcpy(&oneMessage->userId,buf,sizeof(u_int64_t));
        memcpy(&oneMessage->sendTime,buf+sizeof(u_int64_t),sizeof(u_int64_t));
        memcpy(&oneMessage->messageType,buf+sizeof(u_int64_t)*2,sizeof(u_int64_t));
        memcpy(&oneMessage->name,buf+sizeof(u_int64_t)*3,NAME_LEN);
        memcpy(&oneMessage->content,buf+sizeof(u_int64_t)*3+NAME_LEN,CONTENT_LEN);

        //顺序加入
        list_add(&oneMessage->node, &dialogListOne->one_message);
    }
}
void saveOnePerson(char *buf,struct list_head *list,u_int64_t chartId)
{
    int findFlag = 0;
    DIALOG_LIST *dialogListOne = NULL;
    ONE_PERSON *onePerson = NULL;
    list_for_each_entry_reverse(dialogListOne,list,node)
    {
        if(dialogListOne->chartId == chartId)
        {
            findFlag=1;
            break;
        }
	}
    
    //查找有没有重复的元素
    if(dialogListOne != NULL && findFlag)
    {
        findFlag = 1;//表示没找到
        list_for_each_entry_reverse(onePerson,&dialogListOne->one_person,node)
        {
            if(memcmp(buf,&onePerson->userId,sizeof(u_int64_t))==0)
            {
                findFlag = 0;//表示找到了相同的元素
                break;
            }
        }
    }
    onePerson = NULL;
    if(dialogListOne != NULL && findFlag)
    {
        onePerson = malloc(sizeof(ONE_PERSON));
        UTIL_ASSERT(onePerson != NULL);
        memcpy(&onePerson->userId,buf,sizeof(u_int64_t));
        memcpy(&onePerson->joinTime,buf+sizeof(u_int64_t),sizeof(u_int64_t));
        memcpy(&onePerson->name,buf+sizeof(u_int64_t)*2,NAME_LEN);
        list_add_tail(&onePerson->node, &dialogListOne->one_person);
    }
}
void saveOneDialog(char *buf,struct list_head *list)
{
    DIALOG_LIST *strDialog = NULL;
    u_int64_t *pU64 = (u_int64_t *)buf;

    list_for_each_entry_reverse(strDialog,list,node)
    {
        //已经存在则放弃这条数据
        if(strDialog->chartId == pU64[0])
        {
            return;
        }
    }
    strDialog = NULL;
    strDialog = (DIALOG_LIST *)malloc(sizeof(DIALOG_LIST));
    UTIL_ASSERT(strDialog != NULL);
    
    strDialog->chartId = pU64[0];
    strDialog->createTime = pU64[1];
    INIT_LIST_HEAD(&strDialog->one_person);
    INIT_LIST_HEAD(&strDialog->one_message);
    list_add_tail(&strDialog->node,list);
}
/** 
 * thread_get_message
 * @Descript:循环向服务器查询消息
 * @Author	:hezuoqiang
 * @DateTime:2019年11月4日T17:20:41+0800
 * @param	arg	:
 * @RETURN VALUE 
 * @    On success, zero is returned.  On error, negative number is returned
 */
static void *thread_get_message(void *arg)
{
    UTIL_ASSERT(arg != NULL);
    SOCK_BUFFER *pSockBuf = (SOCK_BUFFER *)arg;

    char send_cmd[MAX_BUF_SIZE];
    bzero(send_cmd,MAX_BUF_SIZE);

    //1.先获取10条历史消息
    head_package(send_cmd, E_MSG_GET_HISTORY_MESSAGE, sizeof(u_int64_t)*2+NAME_LEN);
    memcpy((char *)send_cmd + Msg.m_msg_len, &s_client_data->user.id, sizeof(u_int64_t));
    memcpy((char *)send_cmd + Msg.m_msg_len + sizeof(u_int64_t), &s_client_data->user.session, NAME_LEN);
    memcpy((char *)send_cmd + Msg.m_msg_len + sizeof(u_int64_t)+NAME_LEN,&s_client_data->chartId,sizeof(u_int64_t));
    tcp_udp_write(pSockBuf->m_sockfd,pSockBuf->m_sock_type,&pSockBuf->m_in_addr, send_cmd, Msg.m_msg_len+sizeof(u_int64_t)*2+NAME_LEN);
    while(1)
    {
        if(s_client_data->chartId == 0)
            break;
        //2.再循环查询最近的消息,为了完成任务就先不写了，虽然有点浪费cpu,但本来也只是练习用的
        //head_package(send_cmd, E_MSG_GET_LATEST_MESSAGE, sizeof(u_int64_t)*2+NAME_LEN);
        head_package(send_cmd, E_MSG_GET_HISTORY_MESSAGE, sizeof(u_int64_t)*2+NAME_LEN);
        tcp_udp_write(pSockBuf->m_sockfd,pSockBuf->m_sock_type,&pSockBuf->m_in_addr, send_cmd, Msg.m_msg_len+sizeof(u_int64_t)*2+NAME_LEN);
        usleep(200*1000);//没秒刷新5次聊天记录
    }
    return NULL;
}
void chart(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr)
{
    /* 1.检查参数 */
    UTIL_ASSERT(s_client_data->chartId);
    int findFlag=0;
    int ret = 0;
    DIALOG_LIST *dialogListOne = NULL;
    list_for_each_entry_reverse(dialogListOne,&s_client_data->dialogList,node)
    {
        if(dialogListOne->chartId == s_client_data->chartId)
        {
            findFlag=1;
            break;
        }
	}
    UTIL_ASSERT(findFlag);

    /* 2.创建线程向服务器轮询 */
    SOCK_BUFFER sockBuf;
    sockBuf.m_sock_type = socket_type;
    sockBuf.m_sockfd = sockfd;
    memcpy(&sockBuf.m_in_addr,serv_addr,sizeof(struct sockaddr_in));
    pthread_t thread;
    int err = pthread_create(&thread, NULL,thread_get_message, &sockBuf);
    UTIL_ASSERT(err == 0);
    
    /* 3.循环监听套接字与标准输入 */
    printf("\rinput message>");
    fflush(stdout);
    fd_set rfds;
    while (1)
    {
        FD_ZERO(&rfds);
        FD_SET(sockfd, &rfds);
        FD_SET(0, &rfds); //监听标准输入
        err = select(sockfd + 1, &rfds, NULL, NULL, NULL);
        if (err == -1)
        {
            perror("select()");
            exit(-1);
        }
        else if (err)
        {
            if (FD_ISSET(sockfd, &rfds))
            {
                socket_read(sockfd, socket_type,serv_addr);
                //socket_read(sockfd, socket_type, &serv_addr[1]);
            }
            else if (FD_ISSET(0, &rfds))
            {
                ret = chart_stdin_read(sockfd, socket_type,serv_addr);
                if(ret == -1)
                {
                    //退出命令
                    ret = pthread_cancel(thread);
                    UTIL_ASSERT(ret == 0);
                    printClientMenu();
                    return;
                }
            }
            else
            {
                printf("unexpected FD_ISSET() retval\n");
            }
        }
        else
        {
            printf("unexpected select() return %d\n", err);
            exit(-1);
        }
    }
}
/**
 * @brief  处理服务端数据
 * @note   
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @param  cmd_num: 
 * @param  packet_len: 
 * @param  *buf: 
 * @retval 
 */
int deal_client_recv(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr,
                     uint16_t cmd_num, uint32_t packet_len, char *buf)
{
    signed int err = 0;
    switch (cmd_num)
    {
    case E_MSG_DEBUG:
        fprintf(stderr, "\r%s              \ninput command>", buf);
        break;
    case E_MSG_TIMESTAMP:
        //保存时间戳
        fprintf(stderr, "\rreceive timestamp\ninput command>");
        //err = saveTimestamp(buf, packet_len);
        break;
    case E_MSG_WRONG_PASSWD:
        fprintf(stderr, "\r用户名或密码错误\n");
        exit(-1);
        break;
    case E_MSG_SERVER_BUG:
        fprintf(stderr, "\r服务器BUG        \n");
        break;
    case E_MSG_WRONG_SESSION:
        fprintf(stderr, "\rsession 错误      \n");
        exit(-1);
        break;
    case E_MSG_FILE_NAME_ERROR:
        fprintf(stderr, "\r文件或文件夹不存在      \ninput command>");
        break;
    case E_MSG_TRANS_EXIT:
        fprintf(stderr, "\r请等待当前文件传输完      \ninput command>");
        break;
    case E_MSG_FILE_HEAD: //为了程序简单这里直接传了结构体，没有考虑字节序是不符合规范的
        err = save_file_head(sockfd, socket_type, serv_addr, buf, packet_len);
        break;
    case E_MSG_CONTINUE:
        err = continue_send_file_to_server(sockfd, socket_type, serv_addr);
        break;
    case E_MSG_FILE_CONTENT:
        err = save_file_content(buf, packet_len);
        break;
    case E_MSG_FILE_END: //服务器文件发送完了，客户端需要进行校验
        check_file();
        break;
    case E_MSG_FILE_IS_DIR:
        fprintf(stderr, "\r抱歉，暂不支持文件夹下载      \ninput command>");
        break;
    case E_MSG_FILE_IS_EXEC:
        fprintf(stderr, "\r抱歉，正在运行的可执行文件不支持下载  \ninput command>");
        break;
	case E_MSG_REGIST_FILED:
		fprintf(stderr, "注册失败，请确认用户名与密码后重新登录!!\n");
		exit(0);
		break;
	case E_MSG_IDENTIFY_FAILED:
		fprintf(stderr, "身份认证失败，请确认用户名与密码后重新登录!!\n");
		exit(0);
		break;
	case E_MSG_USER_INFO:
		err = save_user_info(buf, packet_len);
		printClientMenu();
		break;
    case E_MSG_GET_DIALOG_ONE:  //一项会话信息
        saveOneDialog(buf,&s_client_data->dialogList);
        break;
    case E_MSG_GET_DIALOG_END:  //会话列表发送完毕,可以尝试打印
        printDialogList(&s_client_data->dialogList);
        break;
    case E_MSG_GET_DIALOG_PERSON_INFO:  //服务器发送的一项参与会话的人员信息
        saveOnePerson(buf,&s_client_data->dialogList,s_client_data->chartId);
        break;
    case E_MSG_ONE_MESSAGE:             //服务器发送的一条会话信息
        saveOneMessage(buf,&s_client_data->dialogList,s_client_data->chartId);
        break;
    case E_MSG_END_DIALOG_PERSON_INFO:  //人员信息发送完毕
        printPersonList(&s_client_data->dialogList,s_client_data->chartId);
        chart(sockfd, socket_type, serv_addr);
        break;
    case E_MSG_END_MESSAGE:             //一组消息发送完毕
        if(s_client_data->messageUpdated)
        {
            printPersonList(&s_client_data->dialogList,s_client_data->chartId);
            printMessages(&s_client_data->dialogList,s_client_data->chartId);
            fprintf(stderr,"\rinput message>");
            s_client_data->messageUpdated = 0;
        }
        break;
    default:
        fprintf(stderr, "unknow cmd_num:%d\n",cmd_num);
        return -1;
    }
    if (err)
    {
        head_package(buf, err, NAME_PASSWD_SESSION_LEN);
        memcpy(buf + Msg.m_msg_len, (char *)s_client_data->m_session, NAME_PASSWD_SESSION_LEN);
        tcp_udp_write(sockfd, socket_type, serv_addr, buf, Msg.m_msg_len + NAME_PASSWD_SESSION_LEN);
    }
    return 0;
}
int chart_stdin_read(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr)
{
    char buf[MAX_BUF_SIZE];
    char send_cmd[MAX_BUF_SIZE];
    bzero(buf, MAX_BUF_SIZE);
    fgets(buf, MAX_BUF_SIZE/2, stdin);
    if (strlen(buf) == 1)
    {
        //检测到回车后重新打印提示命令
        fprintf(stderr, "\rinput message>");
    }
    else if (memcmp(buf, "$exit", strlen(buf)-1) == 0||memcmp(buf, "$quit", strlen(buf)-1) == 0||memcmp(buf, "$q", strlen(buf)-1) == 0)
    {
        return -1;
    }
    else
    {
        buf[strlen(buf)-1] = 0;   //去掉换行
        bzero(send_cmd, MAX_BUF_SIZE);
        memcpy((char *)send_cmd + Msg.m_msg_len, &s_client_data->user.id, sizeof(u_int64_t));
        memcpy((char *)send_cmd + Msg.m_msg_len + sizeof(u_int64_t), &s_client_data->user.session, NAME_LEN);
        memcpy((char *)send_cmd + Msg.m_msg_len + sizeof(u_int64_t)+NAME_LEN,&s_client_data->chartId,sizeof(u_int64_t));
        memcpy((char *)send_cmd + Msg.m_msg_len + sizeof(u_int64_t)*2+NAME_LEN,buf,strlen(buf)+1);
        head_package(send_cmd, E_MSG_USER_SEND_MESSAGE, sizeof(u_int64_t)*2 + NAME_LEN + strlen(buf)+1);  //加上结束符
        int num = tcp_udp_write(sockfd, socket_type, serv_addr, send_cmd, Msg.m_msg_len + sizeof(u_int64_t)*2 + NAME_LEN + strlen(buf)+1);
        assert(num > 0);
    }
    printf("\rinput message>");
    return 0;
}



/**
 * @brief  当标准输入可读时，调用它处理标准输入的命令
 * @note   
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @retval 
 */
int stdin_read(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr)
{
    char buf[MAX_BUF_SIZE];
    bzero(buf, MAX_BUF_SIZE);
    fgets(buf, MAX_BUF_SIZE, stdin);
    int err = 0;
    if (strlen(buf) == 1)
    {
        //检测到回车后重新打印提示命令
        fprintf(stderr, "input command>");
    }
#if 0
    else if (memcmp(buf, "ls", strlen("ls")) == 0)
    {
        send_cmd_server(sockfd, socket_type, serv_addr, E_MSG_BEG_FILE_LIST);
    }
    else if (memcmp(buf, "get", strlen("get")) == 0)
    {
        send_get_cmd_to_server(sockfd, socket_type, serv_addr, E_MSG_GET_FILE, buf + strlen("get"));
    }
    else if (memcmp(buf, "send", strlen("send")) == 0)
    {
        err = send_file_to_server(sockfd, socket_type, serv_addr, buf + strlen("send"));
    }
#endif
	else if (memcmp(buf, "exit", strlen(buf)-1) == 0||memcmp(buf, "quit", strlen(buf)-1) == 0||memcmp(buf, "q", strlen(buf)-1) == 0)
    {
        exit(0);
    }
	else if (memcmp(buf, "help", strlen(buf)-1) == 0)
    {
        printClientMenu();
    }
	else if (memcmp(buf, "info", strlen(buf)-1) == 0)
    {
        printUserInfo(&s_client_data->user);
    }
	else if (memcmp(buf, "dialog", strlen("dialog")) == 0)
    {
        getDialogListFromServer(sockfd, socket_type, serv_addr, buf + strlen("dialog"),&s_client_data->user);
    }
    else
    {
        fprintf(stderr, "\runknow command    \ninput command>");
    }
    if (err)
    {
        head_package(buf, err, 0);
        tcp_udp_write(sockfd, socket_type, serv_addr, buf, Msg.m_msg_len);
    }
    return 0;
}
/**
 * @brief  文件任务结构体初始化
 * !@note  此函数与另一个函数存在很相似，时间关系就不合并了
 * @param  *name: 
 * @retval 
 */
int client_upload_task_init(const char *name)
{
    /* 1.检查是否已经存在任务 */
    if (s_client_data->file_task.up_down != FILE_NONE)
    {
        fprintf(stderr, "\r请等待当前文件传输完成..\ninput command>");
        return E_MSG_TRANS_EXIT;
    }
    /* 2.打开文件，计算校验值 */
    struct stat st;
    stat(name, &st);
    if (S_ISDIR(st.st_mode))
    {
        fprintf(stderr, "\r暂不支持上传文件夹..\ninput command>");
        return E_MSG_FILE_IS_DIR;
    }
    s_client_data->fp = fopen(name, "rb");
    if (s_client_data->fp == NULL)
    {
        fprintf(stderr, "\rfopen %s, %sinput command>\n", name, strerror(errno));
        return E_MSG_FILE_NAME_ERROR;
    }
    bzero(s_client_data->file_task.sha256, 64);
    sha256Universal(get_data, s_client_data->fp, (char *)s_client_data->file_task.sha256);
    assert(fseek(s_client_data->fp, 0, SEEK_END) == 0);
    s_client_data->file_task.file_size = (uint32_t)ftell(s_client_data->fp);
    s_client_data->file_task.finished_size = 0;
    s_client_data->file_task.up_down = FILE_UP;
    memcpy((char *)s_client_data->file_task.file_name, name, strlen(name) + 1);
    FCLOSE(s_client_data->fp);
    return E_MSG_SUCCESS;
}

/**
 * @brief  发送文件
 * !@note  此函数与另一个函数存在很相似，时间关系就不合并了
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @retval 
 */
int send_file_head_to_server(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr)
{
    FILE_TRANS_TASK *file_task = &(s_client_data->file_task);
    char send_cmd[MAX_BUF_SIZE];
    bzero(send_cmd, MAX_BUF_SIZE);

    /* 1.发送文件头，因为时间关系这里就直接发结构体了,暂时没考虑字节序 */
    head_package(send_cmd, E_MSG_FILE_HEAD, NAME_PASSWD_SESSION_LEN + sizeof(FILE_TRANS_TASK));
    memcpy((char *)send_cmd + Msg.m_msg_len, s_client_data->m_session, NAME_PASSWD_SESSION_LEN);
    memcpy((char *)send_cmd + Msg.m_msg_len + NAME_PASSWD_SESSION_LEN, (char *)file_task, sizeof(FILE_TRANS_TASK));
    tcp_udp_write(sockfd, socket_type, serv_addr,
                  send_cmd, Msg.m_msg_len + NAME_PASSWD_SESSION_LEN + sizeof(FILE_TRANS_TASK));

    fprintf(stderr, "正在进行文件传输...\n");
    return E_MSG_SUCCESS;
}

/**
 * @brief  继续发送文件内容
 * !@note  此函数与另一个函数存在很相似，时间关系就不合并了
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @retval 
 */
int continue_send_file_to_server(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr)
{
    FILE_TRANS_TASK *file_task = &(s_client_data->file_task);
    char send_cmd[MAX_BUF_SIZE];

    /* 1.继续发送文件内容 */
    FILE *fp = fopen((char *)file_task->file_name, "rb");
    if (fp == NULL)
    {
        fprintf(stderr, "%s\n", strerror(errno));
        file_task->up_down = FILE_NONE;
        return E_MSG_FILE_NAME_ERROR;
    }
    if (file_task->finished_size < file_task->file_size)
    {
        fseek(fp, file_task->finished_size, SEEK_SET);
        uint32_t send_len = MIN(file_task->file_size - file_task->finished_size,
                                MAX_BUF_SIZE - Msg.m_msg_len - NAME_PASSWD_SESSION_LEN);
        memcpy((char *)send_cmd + Msg.m_msg_len, s_client_data->m_session, NAME_PASSWD_SESSION_LEN);
        int num = fread((char *)send_cmd + Msg.m_msg_len + NAME_PASSWD_SESSION_LEN, 1, send_len, fp);
        if ((unsigned int)num != send_len)
        {
            FCLOSE(fp);
            file_task->up_down = FILE_NONE;
            return E_MSG_FILE_TRANS_ERROR;
        }
        head_package(send_cmd, E_MSG_FILE_CONTENT, NAME_PASSWD_SESSION_LEN + send_len);
        tcp_udp_write(sockfd, socket_type, serv_addr,
                      send_cmd, Msg.m_msg_len + NAME_PASSWD_SESSION_LEN + send_len);
        file_task->finished_size += send_len;
        fprintf(stderr, "\r已发送 %d/%d      ", file_task->finished_size, file_task->file_size);
    }
    else
    {
        /* 3.结束文件传输 */
        head_package(send_cmd, E_MSG_FILE_END, NAME_PASSWD_SESSION_LEN);
        memcpy((char *)send_cmd + Msg.m_msg_len, s_client_data->m_session, NAME_PASSWD_SESSION_LEN);
        tcp_udp_write(sockfd, socket_type, serv_addr,
                      send_cmd, Msg.m_msg_len + NAME_PASSWD_SESSION_LEN);
        fprintf(stderr, "\n文件发送完毕...\n");
        file_task->up_down = FILE_NONE;
        //FCLOSE(fp);
    }
    FCLOSE(fp);
    return E_MSG_SUCCESS;
}
/**
 * @brief  发送文件给服务器
 * !@note  此函数与另一个函数存在很相似，时间关系就不合并了
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @param  *file_name: 
 * @retval 
 */
int send_file_to_server(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr, char *file_name)
{
    /* 1.规范文件名 */
    char *name = file_name;
    while (*name == ' ')
        name++;
    if (name[strlen(name) - 1] == '\n')
    {
        name[strlen(name) - 1] = 0; //添加结束符
    }
    /* 2.文件任务初始化 */
    int retval = client_upload_task_init(name);
    if (retval == E_MSG_SUCCESS)
    {
        //持续文件上传
        retval = send_file_head_to_server(sockfd, socket_type, serv_addr);
    }
    return retval;
}

/** 
 * dialog
 * @Descript:对话窗口
 * @Author	:hezuoqiang
 * @DateTime:2019年11月3日T10:17:08+0800
 * @param	sockfd	:
 * @param	socket_type	:
 * @param	serv_addr	:
 * @param	chart_id	:
 * @RETURN VALUE 
 * @    On success, zero is returned.  On error, negative number is returned
 */
int getDialogListFromServer(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr, char *pChartId,USER *user)
{
	u_int64_t chartId = 0;
 #if _BSD_SOURCE || _SVID_SOURCE || _XOPEN_SOURCE >= 600 || _ISOC99_SOURCE || _POSIX_C_SOURCE >= 200112L
	chartId = (u_int64_t)atoll(pChartId);
 #else
	chartId = (u_int64_t)atol(pChartId);
 #endif

    //printf("%s,%s:%d:chartId = %llu,userId=%llu\n",__FILE__,__FUNCTION__,__LINE__,(unsigned long long)chartId,(unsigned long long)user->id);
    char send_cmd[MAX_BUF_SIZE];
    bzero(send_cmd, MAX_BUF_SIZE);
    memcpy((char *)send_cmd + Msg.m_msg_len, &user->id, sizeof(u_int64_t));
    memcpy((char *)send_cmd + Msg.m_msg_len + sizeof(u_int64_t), user->session, NAME_LEN);
    if(chartId == 0)
    {
        head_package(send_cmd, E_MSG_GET_DIALOG_LIST, sizeof(u_int64_t) + NAME_LEN);
        int num = tcp_udp_write(sockfd, socket_type, serv_addr, send_cmd, Msg.m_msg_len + sizeof(u_int64_t) + NAME_LEN);
        assert((unsigned)num == Msg.m_msg_len + sizeof(u_int64_t) + NAME_LEN);
    }
    else
    {
        head_package(send_cmd, E_MSG_GET_DIALOG_PERSON_INFO, sizeof(u_int64_t) + NAME_LEN + sizeof(u_int64_t));
        memcpy((char *)send_cmd + Msg.m_msg_len + sizeof(u_int64_t) + NAME_LEN, &chartId, sizeof(u_int64_t));
        int num = tcp_udp_write(sockfd, socket_type, serv_addr, send_cmd, Msg.m_msg_len + sizeof(u_int64_t) + NAME_LEN + sizeof(u_int64_t));
        assert((unsigned)num == Msg.m_msg_len + sizeof(u_int64_t) + NAME_LEN + sizeof(u_int64_t));
    }
    //记录当前会话id
    s_client_data->chartId = chartId;
	return 0;
}
/**
 * @brief  发送“获取文件”类型给服务器
 * @note   
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @param  cmd_num: 
 * @param  *file_name: 
 * @retval 
 */
int send_get_cmd_to_server(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr,
                           uint16_t cmd_num, char *file_name)
{
    char send_cmd[MAX_BUF_SIZE];
    bzero(send_cmd, MAX_BUF_SIZE);
    head_package(send_cmd, cmd_num, NAME_PASSWD_SESSION_LEN + strlen(file_name) + 1);
    memcpy((char *)send_cmd + Msg.m_msg_len, s_client_data->m_session, NAME_PASSWD_SESSION_LEN);
    memcpy((char *)send_cmd + Msg.m_msg_len + NAME_PASSWD_SESSION_LEN, file_name, strlen(file_name) + 1);
    int num = tcp_udp_write(sockfd, socket_type, serv_addr, send_cmd, Msg.m_msg_len + NAME_PASSWD_SESSION_LEN + strlen(file_name) + 1); //发送结束符
    assert(num > 0);
    return 0;
}
/**
 * @brief  发送“发送文件”类型消息给服务器
 * @note   
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @param  cmd_num: 
 * @retval 
 */
int send_cmd_server(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr, uint16_t cmd_num)
{
    char send_cmd[MAX_BUF_SIZE];
    bzero(send_cmd, MAX_BUF_SIZE);
    head_package(send_cmd, cmd_num, NAME_PASSWD_SESSION_LEN);
    memcpy((char *)send_cmd + Msg.m_msg_len, s_client_data->m_session, NAME_PASSWD_SESSION_LEN);
    int num = tcp_udp_write(sockfd, socket_type, serv_addr, send_cmd, Msg.m_msg_len + NAME_PASSWD_SESSION_LEN);
    assert((unsigned)num == Msg.m_msg_len + NAME_PASSWD_SESSION_LEN);
    return 0;
}

/**
 * @brief  通用套接字句柄写函数
 * @note   
 * @param  sockfd: 
 * @param  socket_type: 
 * @param  *serv_addr: 
 * @param  *buf: 
 * @param  data_len: 
 * @retval 
 */
int tcp_udp_write(int sockfd, int32_t socket_type, struct sockaddr_in *serv_addr, char *buf, uint32_t data_len)
{
    int write_num = 0;
#ifdef SLEEP_BEFORE_SEND
    sleep(1);
#endif
    if (socket_type == SOCK_STREAM)
    {
        write_num = write(sockfd, buf, data_len);
    }
    else
    {
        write_num = sendto(sockfd, buf, data_len, 0, (struct sockaddr *)serv_addr, sizeof(struct sockaddr_in));
    }
    return write_num;
}

#pragma GCC diagnostic pop



